
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "icm/icmCpuManager.h"

#define SIM_ATTRS (ICM_ATTR_DEFAULT)

//
// Perform platform creation and application simulation using OVPsim
//
static Bool simulate(const char *appName) {

    // initialize OVPsim, enabling verbose mode to get statistics at end
    // of execution
    icmInit(ICM_VERBOSE|ICM_STOP_ON_CTRLC, 0, 0);

	// select library components
	const char *vlnvRoot = NULL; //When NULL use default library
    const char *model       = icmGetVlnvString(vlnvRoot, "ovpworld.org", "processor", "or1k", "1.0", "model");
    const char *semihosting = icmGetVlnvString(vlnvRoot, "ovpworld.org", "semihosting", "or1kNewlib", "1.0", "model");

    // create the processor bus
    icmBusP bus = icmNewBus("busMain", 32);

    // declare a net for connection of the interrupt line
    icmNetP int1 = icmNewNet("int1");
	
	////////////////////////////////////////////////////////////////////////////
    // Memory
    ////////////////////////////////////////////////////////////////////////////

    // create two memory regions mapping all memory except the Interrupters registers
    icmMemoryP mem1 = icmNewMemory("mem1", ICM_PRIV_RWX, 0x3fffffff);
    icmMemoryP mem2 = icmNewMemory("mem2", ICM_PRIV_RWX, 0x7fffffff);

    // connect memories to the bus
    icmConnectMemoryToBus(bus, "sp", mem1, 0xc0000000);
    icmConnectMemoryToBus(bus, "sp", mem2, 0x00000000);
    

    ////////////////////////////////////////////////////////////////////////////
    // Processor
    ////////////////////////////////////////////////////////////////////////////

    // create a processor instance
    icmProcessorP processor = icmNewProcessor(
        "CPU1",             // CPU name
        "or1k",             // CPU type
        0,                  // CPU cpuId
        0,                  // CPU model flags
        32,                 // address bits
        model,              // model file
        "modelAttrs",       // morpher attributes
        SIM_ATTRS,          // attributes
        0,                  // user-defined attributes
        semihosting,        // semi-hosting file
        "modelAttrs"        // semi-hosting attributes
    );

    // connect the processor instruction and data busses to the bus
    icmConnectProcessorBusses(processor, bus, bus);

    // connect the processor interrupt port to the net
    icmConnectProcessorNet(processor, int1, "intr3", ICM_INPUT);

    // load the application executable file into processor memory space
    if(!icmLoadProcessorMemory(processor, appName, False, False, True)) {
        return False;
    }

	
	////////////////////////////////////////////////////////////////////////////
    // INTERRUPTER Peripheral
    ////////////////////////////////////////////////////////////////////////////
    // instantiate the peripheral
	icmPseP interrupter = icmNewPSE("interrupter", "peripheral/pse.pse", NULL, NULL, NULL);
	
	// connect the BYTEREAD slave port on the bus and define the address range it occupies
    icmConnectPSEBus(interrupter, bus, "BYTEREADCSP", False, 0x80000000, 0x80000020);
	
	//connect interrupter interrupt port to the net
    icmConnectPSENet(interrupter, int1, "intp", ICM_OUTPUT);

    ////////////////////////////////////////////////////////////////////////////
    // Simulation of Platform
    ////////////////////////////////////////////////////////////////////////////

    // Reset the start PC to the reset vector
     unsigned int setPC = 0x00000100;
     icmWriteReg(processor, "PC", &setPC);

    // simulate the platform
    icmProcessorP final = icmSimulatePlatform();

    // was simulation interrupted or did it complete
    if(final && (icmGetStopReason(final)==ICM_SR_INTERRUPT)) {
        icmPrintf("*** simulation interrupted\n");
    }

    icmTerminate();

    return True;

}

//
// Main routine
//
int main(int argc, char **argv) {

    Bool        promptAtExit = False;
    char        appChars[1024];
    const char *appName;

    // Check arguments for application to load
    if(argc==1) {
        // prompt for application to load

        promptAtExit = True;

        do {
            icmPrintf("Enter application elf file name > ");
            fgets(appChars, sizeof(appChars), stdin);

            // trim trailing '\n' and any trailing whitespace
            char *last = appChars + strlen(appChars) - 1;
            while((last>=appChars) && isspace(*last)) {
                *last-- = '\0';
            }

        } while(appChars[0] == '\0');

        appName = appChars;

    } else if(argc==2) {
        // application provided on command line

        appName = argv[1];

    } else {
        // incorrect arguments

        icmPrintf("Usage : %s <application name>\n", argv[0]);
        return -1;
    }

    // call OVPsim platform creation and run simulation
    Bool result = simulate(appName);

    // wait for key press before terminating
    if(promptAtExit) {
        icmPrintf("Press enter to exit demo > ");
        fgets(appChars, sizeof(appChars), stdin);
    }

    return result ? 0 : -1;
}
